package hc.j2me;

import hc.core.ContextManager;
import hc.core.L;
import hc.core.Message;
import hc.core.MsgBuilder;
import hc.core.RootConfig;
import hc.core.UDPPacketResender;
import hc.core.sip.ISIPContext;
import hc.core.sip.SIPManager;
import hc.core.util.CUtil;
import hc.core.util.LogManager;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;
import javax.microedition.io.SocketConnection;
import javax.microedition.io.UDPDatagramConnection;

public class J2MESIPContext extends ISIPContext {
	public J2MESIPContext() {
		resender = new UDPPacketResender(){
			protected final void resend(Object packet) {
				try{
//					hc.core.L.V=hc.core.L.O?false:LogManager.log("Resend packet [" + ((Datagram)packet).getData()[MsgBuilder.INDEX_CTRL_TAG] + "]");
					if(socket != null){
						socket.send((Datagram)packet);
					}
				}catch (Exception e) {
					e.printStackTrace();
				}
			}

			public final Object getUDPSocket(){
				return socket;
			}
			
			UDPDatagramConnection socket;
			private String udpTargetAddr;
			
			public void setUDPTargetAddress(Object address, int port){
				udpTargetAddr = (String)address;
			}
			
			public final void sendUDP(final byte ctrlTag, byte subCtrlTag, final byte[] jcip_bs, int offset, final int j_len, final int mtuLen, final boolean isFlushNow) {
				int len = j_len;
				final int len_msg_data = (mtuLen==0?real_len_upd_data:mtuLen);
				final int split_num = len/ len_msg_data + ((len%len_msg_data > 0)?1:0);
				int groupID = 0;
				int SplitNo = 0;
				do{
					final int realLen = (len>len_msg_data)?len_msg_data:len;

					final Datagram p = (Datagram)cacher.getFree();
					p.reset();
					final byte[] bs = p.getData();

					bs[IDX_HEADER_1] = UDP_HEADER[0];
					bs[IDX_HEADER_2] = UDP_HEADER[1];
					
//					Message.setSendUUID(bs, selfUUID, selfUUID.length);
					Message.setMsgBody(bs, MsgBuilder.INDEX_UDP_MSG_DATA, jcip_bs, offset, realLen);

					//生成序列号
					groupID = Message.setSplitPara(bs, groupID, SplitNo++, split_num);
					
					bs[MsgBuilder.INDEX_CTRL_TAG] = ctrlTag;
					bs[MsgBuilder.INDEX_CTRL_SUB_TAG] = subCtrlTag;

					p.setLength(realLen + MsgBuilder.INDEX_UDP_MSG_DATA);
					
					//属于分包型
					if(realLen == j_len){
						bs[MsgBuilder.INDEX_PACKET_SPLIT] = MsgBuilder.DATA_PACKET_NOT_SPLIT;
					}else{
						bs[MsgBuilder.INDEX_PACKET_SPLIT] = MsgBuilder.DATA_PACKET_SPLIT;
					}

					try {
//						L.V = L.O ? false : LogManager.log("Send UDP["+ctrlTag+"] to " + udpTargetAddr);
						p.setAddress(udpTargetAddr);
					} catch (IOException e) {
						e.printStackTrace();
						cacher.cycle(p);
						return;
					}
					
					try{
		        		if(realLen == 0 || ctrlTag <= MsgBuilder.UN_XOR_MSG_TAG_MIN){
		        			
		        		}else{
			        		if(CUtil.userEncrypter != null){
			        			CUtil.userEncrypter.encryptData(bs, MsgBuilder.INDEX_UDP_MSG_DATA, realLen);
			        		}
			        		CUtil.xor(bs, MsgBuilder.INDEX_UDP_MSG_DATA, realLen, CUtil.OneTimeCertKey);
		        		}
		        		if(ctrlTag != MsgBuilder.E_TAG_ACK){
		        			resender.needAckAtSend(p, Message.getAndSetAutoMsgID(bs));
		        		}
		        		if(isFlushNow){
		        			socket.send(p);
		        		}
			    		if(ctrlTag == MsgBuilder.E_TAG_ACK){
		        			cacher.cycle(p);
		        		}
					}catch (Exception e) {
						//从严格意义上，以下应回收。但是考虑性能和大多数情形下，即使发生也是可以忽略的
//						cacher.cycle(p);

						e.printStackTrace();
					}

//					cacher.cycle(p);
					
					len -= realLen;
					offset += realLen;
				}while(len > 0);
			}	

			public final void setUDPSocket(Object sender) {
				if(socket != null){
					try{
						socket.close();
					}catch (Exception e) {
						
					}
					socket = null;
				}
				socket = (UDPDatagramConnection)sender;
			}};
	}
	
	public final void setDatagramLength(Object dp, int len){
		final Datagram datagram = (Datagram)dp;
		datagram.reset();
		datagram.setLength(len);
	}
	
	public final byte[] getDatagramBytes(Object dp) {
		return ((Datagram)dp).getData();
	}
	
	public final Object getDatagramPacket(Object dp) {
		try {
			if(dp == null){
				dp = ((UDPDatagramConnection)resender.getUDPSocket()).newDatagram(MsgBuilder.UDP_BYTE_SIZE);//udpAddress
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return dp;
	}
	

//	public final void setUDPTargetAddress(String add){
//		
//	}
	

	private SocketConnection usingSocket;
	
	public String getSTUNServerAndPort() {
		return Starter.config.stunAndPort;
	}

	public boolean buildUDPChannel(int targetPort) {
		if(Starter.config.isTCPOnly){
			L.V = L.O ? false : LogManager.log("TCP Only : true, skip build UDP channel");
			return false;
		}
		
		try {
			final String address = "datagram://" + SIPManager.relayIpPort.ip + ":" + targetPort;
			UDPDatagramConnection send = (UDPDatagramConnection)Connector.open(address, Connector.READ_WRITE);
			resender.setUDPTargetAddress(address, 0);
			resender.setUDPSocket(send);
			
			ContextManager.getContextInstance().getUDPReceiveServer().setUdpServerSocket(send);
			
			L.V = L.O ? false : LogManager.log("Succ build UDP to " + address);
			return true;
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return false;
	}


	private DataInputStream is;
	private DataOutputStream os;
	public DataInputStream getInputStream(Object socket) throws IOException{
		if(is == null){
			is = ((SocketConnection)socket).openDataInputStream();
		}
		return is;
	}
	
	public DataOutputStream getOutputStream(Object socket) throws IOException{
		if(os == null){
			os = ((SocketConnection)socket).openDataOutputStream();
		}
		return os;
	}
	
	public void closeSocket(Object socket) throws IOException{
		L.V = L.O ? false : LogManager.log("Close Socket now");
		isClose = true;
		is = null;
		os = null;
		((SocketConnection)socket).close();
	}

	public void saveStunServerAndPort(String serverAndPort) {
		Starter.config.stunAndPort = serverAndPort;
		Starter.update(Starter.config);
	}

//	public StunDesc stun(Object iaddress, String stunServer, int stunServerPort, int udpPort) {
//		SocketConnection send = null;
//		StunDesc sd = new StunDesc() {
//			
////			public void keepalive() {
////			}
//			
//		};
//		try{
//			send = (SocketConnection)SIPManager.getSIPContext().buildSocket(0, stunServer, stunServerPort);//new DatagramSocket(new InetSocketAddress(iaddress, 0));
//			
//			sd.setSocket(send);
//
//			DataNatReqConn nrn = new DataNatReqConn();
//			byte[] bs = DatagramPacketCacher.getInstance().getFree();
//			
//			nrn.setBytes(bs);
//			nrn.setRemotePort(0);
//			
//			if(SIPManager.isOnRelay()){
//				bs[MsgBuilder.INDEX_SERVER_ON_RELAY] = MsgBuilder.DATA_IS_CLIENT_TO_RELAY;
//			}
//			bs[MsgBuilder.INDEX_CTRL_TAG] = MsgBuilder.E_TAG_RELAY_REG;
//
//			
//			bs[MsgBuilder.INDEX_PROTOCAL_TAG] = IConstant.DATA_PROTOCAL_HEAD_H;
//			bs[MsgBuilder.INDEX_PROTOCAL_TAG + 1] = IConstant.DATA_PROTOCAL_HEAD_C;
//			
//
//			bs[MsgBuilder.INDEX_PACKET_SPLIT] = MsgBuilder.DATA_PACKET_NOT_SPLIT;
//
//			OutputStream openOutputStream = send.openOutputStream();
//			openOutputStream.write(bs, 0, nrn.getLength() + MsgBuilder.INDEX_MSG_DATA);
//			openOutputStream.flush();
//			
////			LogManager.info("Send to Root");
////			send.receive(dp);
////			sd.setPublicIP(nrn.getRemoteIP());
////			sd.setPublicPort(nrn.getRemotePort());
////			sd.setSymmetric();
//			
//			DatagramPacketCacher.getInstance().cycle(bs);
//			
//			return sd;
//		}catch (Exception e) {
//			LogManager.err(e.getMessage());
//		}
//		
//		return null;
//	}

	public void closeDeploySocket() {
		L.V = L.O ? false : LogManager.log("Close Deploy Socket now");
		is = null;
		os = null;

		SocketConnection old = usingSocket;
		deploySocket(null, null, null);
		try{
			old.close();
			old = null;
		}catch (Exception e) {
			
		}
	}

	public void setSocket(Object connector) {
		if(usingSocket != null){
			try{
				usingSocket.close();
			}catch (Exception e) {
				
			}
		}
		isClose = false;
		usingSocket = (SocketConnection)connector;
	}

	public Object getSocket() {
		return usingSocket;
	}

	public Object buildSocket(final int localPort, final String targetServer, final int targetPort){
		try {
			J2METimeOutConnector timer = new J2METimeOutConnector(targetServer, targetPort);
			final long curr = System.currentTimeMillis();
			timer.start();
			
			SocketConnection s;
			try{
				do{
					Thread.sleep(100);
					if(timer.socket != null){
						s = timer.socket;
						
						break;
					}
					
					if(System.currentTimeMillis() - curr > 2500){
						L.V = L.O ? false : LogManager.log("J2ME socket build timeout");
						return null;
					}
				}while(true);
			}catch (Exception e1) {
				return null;
			}
			
			final int maxSize = (Starter.config.height>Starter.config.width?Starter.config.height:Starter.config.width);
			final int doubleNum = (maxSize<=120)?1:((maxSize<=240)?2:((maxSize<=320)?4:8));
			int rsize = RootConfig.getInstance().getIntProperty(RootConfig.p_ReceiveBufferSize);
			if(rsize != 0){
				s.setSocketOption(SocketConnection.RCVBUF, rsize * doubleNum);
			}
			
			int ssize = RootConfig.getInstance().getIntProperty(RootConfig.p_SendBufferSize);
			if(ssize != 0){
				s.setSocketOption(SocketConnection.SNDBUF, ssize);
			}
			
//			L.V = L.O ? false : LogManager.log("RcvBuf " + s.getSocketOption(SocketConnection.RCVBUF));
//			L.V = L.O ? false : LogManager.log("SndBuf " + s.getSocketOption(SocketConnection.SNDBUF));
			
			//KeepAlive_Tag
			//Set to zero to disable Nagle algorithm for small buffer operations. Set to a non-zero value to enable.
			s.setSocketOption(SocketConnection.DELAY, 0);
			
			//某些机器出现TCP断线后，如果不能主动KeepAlive，则会长时间不会自动触发ReceiveServer断线事件，所以开启本功能
			s.setSocketOption(SocketConnection.KEEPALIVE, 1);
			s.setSocketOption(SocketConnection.LINGER, 5);
			
			return s;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}

class J2METimeOutConnector extends Thread {
	String ip;
	int port;
	
	J2METimeOutConnector(String ip, int port){
		this.ip = ip;
		this.port = port;
	}
	SocketConnection socket;
	public void run() {
		try{
		socket = (SocketConnection)Connector.open(
				"socket://" + ip + ":" + port//,// + ";ConnectionTimeout="+ timeoutInitValue 
				);//Connector.READ_WRITE, false);
		}catch (Exception e) {
			
		}
	}
	
}